/*
 * Settings.cs
 *
 * Settings for the library.
 *
 * Copyright (c) 2005, dnAnalytics. All rights reserved.
 */

#region Using Directives
using System;
using System.IO;
using System.Xml;
using System.Globalization;
using dnAnalytics.Resources;
using dnAnalytics.LinearAlgebra;
using dnAnalytics.LinearAlgebra.Native;
#endregion Using Directives

namespace dnAnalytics
{
    /// <summary>
    /// This class is used to configure the dnAnalytics Numerical Library.
    /// </summary>
    public static class Settings
    {
        #region Fields
        private static bool native;
        private static BlasProvider blasProvider = BlasProvider.Mkl;
        private static LapackProvider lapackProvider = LapackProvider.Mkl;
        private static MatrixType matrixType = MatrixType.Dense;
        private static VectorType vectorType = VectorType.Dense;
        private static readonly Object lockObject = new Object();
        #endregion Fields

        #region Public Methods
        /// <summary>
        /// Gets and sets the default <see cref="MatrixType"/> to use with <see cref="MatrixBuilder"/>. 
        /// The property defaults to <see cref="MatrixType.Dense"/>.
        /// </summary>
        public static MatrixType DefaultMatrixType
        {
            get
            {
                return matrixType;
            }
            set
            {
                matrixType = value;
            }
        }

        /// <summary>
        /// Gets and sets the default <see cref="VectorType"/> to use with <see cref="VectorBuilder"/>. 
        /// The property defaults to <see cref="VectorType.Dense"/>.
        /// </summary>
        public static VectorType DefaultVectorType
        {
            get
            {
                return vectorType;
            }
            set
            {
                vectorType = value;
            }
        }

        /// <summary>
        /// Configures the library to use native BLAS and LAPACK or not.
        /// </summary>
        public static bool UseNativeLibrary
        {
            get { return native; }
            set { native = value; }
        }

        /// <summary>
        /// Specifies which native <see cref="BlasProvider"/> to use.
        /// </summary>
        public static BlasProvider Blas
        {
            get { return blasProvider; }
            set
            {
                blasProvider = value;
                switch (blasProvider)
                {
                    case BlasProvider.Mkl:
                        Provider.BlasInstance = dnAnalytics.LinearAlgebra.Native.Mkl.Blas.Instance;
                        break;
                    case BlasProvider.Atlas:
                        Provider.BlasInstance = dnAnalytics.LinearAlgebra.Native.AtlasClapack.Blas.Instance;
                        break;
                    case BlasProvider.Acml:
                        Provider.BlasInstance = dnAnalytics.LinearAlgebra.Native.Acml.Blas.Instance;
                        break;
                }
            }
        }

        /// <summary>
        /// Specifies which native <see cref="LapackProvider"/> to use.
        /// </summary>
        public static LapackProvider Lapack
        {
            get { return lapackProvider; }
            set
            {
                lapackProvider = value;
                switch (lapackProvider)
                {
                    case LapackProvider.Mkl:
                        Provider.LapackInstance = dnAnalytics.LinearAlgebra.Native.Mkl.Lapack.Instance;
                        break;
                    case LapackProvider.Clapack:
                        Provider.LapackInstance = dnAnalytics.LinearAlgebra.Native.AtlasClapack.Lapack.Instance;
                        break;
                    case LapackProvider.Acml:
                        Provider.LapackInstance = dnAnalytics.LinearAlgebra.Native.Acml.Lapack.Instance;
                        break;
                    case LapackProvider.AcmlIFort:
                        Provider.LapackInstance = dnAnalytics.LinearAlgebra.Native.Acml.LapackIFort.Instance;
                        break;
                }
            }
        }

        /// <summary>
        /// Loads the library settings from a file.
        /// </summary>
        /// <param name="fileName">The path and file name of the settings file to load.</param>
        /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
        /// <exception cref="ArgumentException"><paramref name="fileName"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.InvalidPathChars"/>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is null.</exception>
        /// <exception cref="PathTooLongException"><paramref name="fileName"/> exceeds the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
        /// <exception cref="FileNotFoundException">The file specified by <paramref name="fileName"/> was not found.</exception>
        /// <exception cref="NotSupportedException">The path specified by <paramref name="fileName"/> is in an invalid format.</exception>
        /// <exception cref="DirectoryNotFoundException">The path specified by <paramref name="fileName"/> is invalid (for example, it is on an unmapped drive).</exception>
        /// <exception cref="InvalidDataException">The file is not a valid settings file.</exception>
        /// <example><para>The file is an XML file containing a list of setting elements inside of a setting element. Each setting element 
        /// contains a key and a value attribute. Each key corresponds to one of the properties of the <b>Setting</b> class. Both the keys
        /// and values are case insensitive.</para>
        /// <pre>
        /// <settings>
        ///     <setting key="useNativeLibrary" value="true"/>
        ///     <setting key="blasProvider" value="MKL"/>
        ///     <setting key="lapackProvider" value="MKL"/>
        ///     <setting key="defaultMatrixType" value="dense"/>
        ///     <setting key="defaultVectorType" value="dense"/>
        /// </settings>
        /// </pre>
        /// </example>
        /// <remarks>Invalid settings are ignored.</remarks>
        public static void Load(string fileName)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("fileName", Strings.NullParameterException);
            }
            Stream fileStream = File.OpenRead(fileName);
            Load(fileStream);
            fileStream.Close();
        }

        /// <summary>
        /// Loads the library settings from a <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> that contains the configuration XML.</param>
        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
        /// <exception cref="InvalidDataException">The stream does not contain a conforming XML stream.</exception>
        /// <example><para>The stream is an XML stream containing a list of setting elements inside of a setting element. Each setting element 
        /// contains a key and a value attribute. Each key corresponds to one of the properties of the <b>Setting</b> class. Both the keys
        /// and values are case insensitive.</para>
        /// <pre>
        /// <settings>
        ///     <setting key="UseNativeLibrary" value="true"/>
        ///     <setting key="BlasProvider" value="MKL"/>
        ///     <setting key="LapackProvider" value="MKL"/>
        ///     <setting key="DefaultMatrixType" value="dense"/>
        ///     <setting key="DefaultVectorType" value="dense"/>
        /// </settings>
        /// </pre>
        /// </example>
        /// <remarks>Invalid settings are ignored.</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1807:AvoidUnnecessaryStringCreation", MessageId = "stack0")]
        public static void Load(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream", Strings.NullParameterException);
            }

            XmlDocument doc = new XmlDocument();
            try
            {
                doc.Load(stream);
                XmlNodeList list = doc.GetElementsByTagName("setting");
                lock (lockObject)
                {
                    foreach (XmlElement node in list)
                    {
                        string key = node.GetAttribute("key").ToLower(CultureInfo.InvariantCulture);
                        string value = node.GetAttribute("value").ToLower(CultureInfo.InvariantCulture);
                        switch (key)
                        {
                            case "usenativelibrary":
                                SetUseNativeLibrary(value);
                                break;
                            case "blasprovider":
                                SetBlasProvider(value);
                                break;
                            case "lapackprovider":
                                SetLapackProvider(value);
                                break;
                            case "defaultmatrixtype":
                                SetDefaultMatrixType(value);
                                break;
                            case "defaultvectortype":
                                SetDefaultVectorType(value);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            catch (XmlException)
            {
                throw new InvalidDataException(Strings.FileInvalidData);
            }
        }

        /// <summary>
        /// Saves the current settings to a file with the given name. The settings can then
        /// be loaded with the <see cref="Settings.Load(string)"/> method.
        /// </summary>
        /// <param name="fileName">The path to and name of the file to save the setting to.</param>
        /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
        /// <exception cref="ArgumentException"><paramref name="fileName"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.InvalidPathChars"/>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="fileName"/> is null.</exception>
        /// <exception cref="PathTooLongException"><paramref name="fileName"/> exceeds the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
        /// <exception cref="NotSupportedException">The path specified by <paramref name="fileName"/> is in an invalid format.</exception>
        /// <exception cref="IOException">An I/O error occurred while creating the file.</exception>
        /// <exception cref="DirectoryNotFoundException">The path specified by <paramref name="fileName"/> is invalid (for example, it is on an unmapped drive).</exception>
        /// <remarks>The method creates a new file or overwrites an existing one.</remarks>
        public static void Save(string fileName)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("fileName", Strings.NullParameterException);
            }
            Stream fileStream = File.Create(fileName);
            Save(fileStream);
            fileStream.Close();
        }

        /// <summary>
        /// Saves the current settings to the given <see cref="Stream"/>. The settings can then
        /// be loaded with the <see cref="Settings.Load(Stream)"/> method.
        /// </summary>
        /// <param name="stream">The <see cref="Stream"/> to save the setting to.</param>
        /// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
        public static void Save(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream", Strings.NullParameterException);
            }

            lock (lockObject)
            {
                StreamWriter sw = new StreamWriter(stream);
                sw.WriteLine("<settings version=\"0.2\">");
                sw.Write("\t<setting key=\"UseNativeLibrary\" value=\"");
                sw.Write(UseNativeLibrary.ToString());
                sw.WriteLine("\"/>");

                sw.Write("\t<setting key=\"BlasProvider\" value=\"");
                sw.Write(Blas.ToString("F"));
                sw.WriteLine("\"/>");

                sw.Write("\t<setting key=\"LapackProvider\" value=\"");
                sw.Write(Lapack.ToString("F"));
                sw.WriteLine("\"/>");

                sw.Write("\t<setting key=\"DefaultMatrixType\" value=\"");
                sw.Write(DefaultMatrixType.ToString("F"));
                sw.WriteLine("\"/>");

                sw.Write("\t<setting key=\"DefaultVectorType\" value=\"");
                sw.Write(DefaultVectorType.ToString("F"));
                sw.WriteLine("\"/>");

                sw.WriteLine("</settings>");
                sw.Flush();
                sw.Close();
            }
        }
        #endregion Public Methods

        #region Private Methods
        /// <summary>
        /// Sets the value of UseNativeLibrary.
        /// </summary>
        private static void SetUseNativeLibrary(string value)
        {
            switch (value)
            {
                case "true":
                case "yes":
                case "1":
                    UseNativeLibrary = true;
                    break;
                case "false":
                case "no":
                case "0":
                    UseNativeLibrary = false;
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Sets the value of BlasProvider.
        /// </summary>
        private static void SetBlasProvider(string value)
        {
            switch (value)
            {
                case "mkl":
                    Blas = BlasProvider.Mkl;
                    break;
                case "atlas":
                    Blas = BlasProvider.Atlas;
                    break;
                case "acml":
                    Blas = BlasProvider.Acml;
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Sets the value of LapackProvider.
        /// </summary>
        private static void SetLapackProvider(string value)
        {
            switch (value)
            {
                case "mkl":
                    Lapack = LapackProvider.Mkl;
                    break;
                case "clapack":
                    Lapack = LapackProvider.Clapack;
                    break;
                case "acml":
                    Lapack = LapackProvider.Acml;
                    break;
                case "acmlifort":
                    Lapack = LapackProvider.AcmlIFort;
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Sets the value of DefaultMatrixType.
        /// </summary>
        private static void SetDefaultMatrixType(string value)
        {
            switch (value)
            {
                case "dense":
                    DefaultMatrixType = MatrixType.Dense;
                    break;
                case "sparse":
                    DefaultMatrixType = MatrixType.Sparse;
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Sets the value of DefaultVectorType.
        /// </summary>
        private static void SetDefaultVectorType(string value)
        {
            switch (value)
            {
                case "dense":
                    DefaultVectorType = VectorType.Dense;
                    break;
                case "sparse":
                    DefaultVectorType = VectorType.Sparse;
                    break;
                default:
                    break;
            }
        }

        #endregion Private Methods
    }
}
